"""
书籍推荐系统模块
提供基于协同过滤和内容的推荐算法
"""

import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from collections import defaultdict
from django.db.models import Count, Avg, Q
from .models import Book, BookInstance, Review, Favorite, BookLike, User


class BookRecommender:
    """
    图书推荐系统类
    包含多种推荐算法
    """
    
    def __init__(self):
        self.user_item_matrix = None
        self.item_similarity_matrix = None
        self.book_features = {}
        self.user_profiles = {}
    
    def build_user_item_matrix(self):
        """构建用户-物品矩阵，用于协同过滤"""
        all_users = User.objects.filter(review__isnull=False).distinct()
        all_books = Book.objects.all()
        
        # 创建用户-物品评分矩阵
        user_item_matrix = np.zeros((len(all_users), len(all_books)))
        
        for i, user in enumerate(all_users):
            for j, book in enumerate(all_books):
                try:
                    rating = Review.objects.get(user=user, book=book).rating
                    user_item_matrix[i, j] = rating
                except Review.DoesNotExist:
                    # 如果用户有收藏但没有评分，给一个较高的默认分
                    if Favorite.objects.filter(user=user, book=book).exists():
                        user_item_matrix[i, j] = 4
                    # 如果用户有点赞但没有评分，给一个中等的默认分
                    elif BookLike.objects.filter(user=user, book=book).exists():
                        user_item_matrix[i, j] = 3.5
        
        self.user_item_matrix = user_item_matrix
        return user_item_matrix
    
    def calculate_item_similarity(self):
        """计算物品相似度矩阵"""
        if self.user_item_matrix is None:
            self.build_user_item_matrix()
        
        # 使用余弦相似度计算物品之间的相似性
        item_similarity = cosine_similarity(self.user_item_matrix.T)
        self.item_similarity_matrix = item_similarity
        return item_similarity
    
    def extract_book_features(self):
        """提取书籍特征用于基于内容的推荐"""
        books = Book.objects.all().prefetch_related('genre', 'author')
        
        for book in books:
            # 提取书籍特征
            features = {
                'genre_ids': [genre.id for genre in book.genre.all()],
                'author_id': book.author.id if book.author else None,
                'avg_rating': book.average_rating(),
                'popularity': book.likes + book.rating_count * 2  # 简单的流行度计算
            }
            self.book_features[book.id] = features
        
        return self.book_features
    
    def build_user_profile(self, user):
        """构建用户兴趣画像"""
        if not user.is_authenticated:
            return None
            
        # 获取用户交互的书籍
        rated_books = Review.objects.filter(user=user)
        favorited_books = Favorite.objects.filter(user=user)
        liked_books = BookLike.objects.filter(user=user)
        
        # 提取用户对各个类别的兴趣权重
        genre_weights = defaultdict(float)
        author_weights = defaultdict(float)
        
        # 根据评分计算兴趣权重
        for review in rated_books:
            for genre in review.book.genre.all():
                genre_weights[genre.id] += review.rating / 5.0  # 归一化评分
            
            if review.book.author:
                author_weights[review.book.author.id] += review.rating / 5.0
        
        # 收藏的书籍增加额外权重
        for favorite in favorited_books:
            for genre in favorite.book.genre.all():
                genre_weights[genre.id] += 0.8  # 收藏行为的权重
            
            if favorite.book.author:
                author_weights[favorite.book.author.id] += 0.8
        
        # 点赞的书籍增加权重
        for like in liked_books:
            for genre in like.book.genre.all():
                genre_weights[genre.id] += 0.5  # 点赞行为的权重
            
            if like.book.author:
                author_weights[like.book.author.id] += 0.5
        
        # 归一化权重
        total_genre_weight = sum(genre_weights.values()) or 1
        total_author_weight = sum(author_weights.values()) or 1
        
        for genre_id in genre_weights:
            genre_weights[genre_id] /= total_genre_weight
            
        for author_id in author_weights:
            author_weights[author_id] /= total_author_weight
        
        # 保存用户画像
        self.user_profiles[user.id] = {
            'genre_preferences': dict(genre_weights),
            'author_preferences': dict(author_weights),
            'interaction_count': rated_books.count() + favorited_books.count() + liked_books.count()
        }
        
        return self.user_profiles[user.id]
    
    def recommend_collaborative_filtering(self, user, n=5):
        """基于协同过滤的推荐算法"""
        if not user.is_authenticated:
            return self._get_popular_books(n)
            
        # 获取用户已评价的书籍
        rated_books = Review.objects.filter(user=user).values_list('book__id', flat=True)
        favorited_books = Favorite.objects.filter(user=user).values_list('book__id', flat=True)
        liked_books = BookLike.objects.filter(user=user).values_list('book__id', flat=True)
        
        # 合并用户交互过的所有书籍ID
        interacted_book_ids = set(list(rated_books) + list(favorited_books) + list(liked_books))
        
        if not interacted_book_ids:
            return self._get_popular_books(n)
            
        # 如果没有构建相似度矩阵，先构建
        if self.item_similarity_matrix is None:
            self.calculate_item_similarity()
            
        # 如果矩阵是空的（可能因为数据不足），返回热门书籍
        if self.item_similarity_matrix is None or self.item_similarity_matrix.size == 0:
            return self._get_popular_books(n)
        
        # 获取所有书籍
        all_books = list(Book.objects.all())
        
        # 计算推荐得分
        scores = {}
        for i, book_i in enumerate(all_books):
            if book_i.id in interacted_book_ids:
                continue  # 跳过用户已经交互过的书籍
                
            score = 0
            for j, book_j in enumerate(all_books):
                if book_j.id not in interacted_book_ids:
                    continue
                    
                if i >= len(self.item_similarity_matrix) or j >= len(self.item_similarity_matrix[0]):
                    continue  # 避免索引越界
                    
                # 查找用户对book_j的评分
                try:
                    rating = Review.objects.get(user=user, book=book_j).rating
                except Review.DoesNotExist:
                    if book_j.id in favorited_books:
                        rating = 4  # 默认收藏相当于4分
                    elif book_j.id in liked_books:
                        rating = 3.5  # 默认点赞相当于3.5分
                    else:
                        rating = 0
                
                # 加权计算得分
                similarity = self.item_similarity_matrix[i][j]
                score += similarity * rating
            
            scores[book_i.id] = score
        
        # 选出得分最高的n本书
        recommended_book_ids = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:n]
        recommended_books = []
        
        for book_id, score in recommended_book_ids:
            try:
                book = Book.objects.get(id=book_id)
                recommended_books.append(book)
            except Book.DoesNotExist:
                continue
                
        return recommended_books
    
    def recommend_content_based(self, user, n=5):
        """基于内容的推荐算法"""
        if not user.is_authenticated:
            return self._get_popular_books(n)
            
        # 构建用户画像
        user_profile = self.build_user_profile(user)
        if not user_profile or user_profile['interaction_count'] == 0:
            return self._get_popular_books(n)
            
        # 如果没有提取书籍特征，先提取
        if not self.book_features:
            self.extract_book_features()
            
        # 获取用户已交互的书籍ID
        interacted_book_ids = set(
            list(Review.objects.filter(user=user).values_list('book__id', flat=True)) +
            list(Favorite.objects.filter(user=user).values_list('book__id', flat=True)) +
            list(BookLike.objects.filter(user=user).values_list('book__id', flat=True))
        )
        
        # 计算每本书的相似度得分
        book_scores = {}
        for book_id, features in self.book_features.items():
            if book_id in interacted_book_ids:
                continue  # 跳过用户已经交互过的书籍
                
            score = 0
            # 基于类别的匹配分数
            for genre_id in features['genre_ids']:
                if genre_id in user_profile['genre_preferences']:
                    score += user_profile['genre_preferences'][genre_id] * 1.5  # 给类别更高权重
                    
            # 基于作者的匹配分数
            if features['author_id'] and features['author_id'] in user_profile['author_preferences']:
                score += user_profile['author_preferences'][features['author_id']] * 1.2
                
            # 考虑书籍整体质量和流行度
            score += features['avg_rating'] * 0.2
            score += (features['popularity'] / 100) * 0.1  # 归一化流行度影响
                
            book_scores[book_id] = score
            
        # 选出得分最高的n本书
        recommended_book_ids = sorted(book_scores.items(), key=lambda x: x[1], reverse=True)[:n]
        recommended_books = []
        
        for book_id, score in recommended_book_ids:
            try:
                book = Book.objects.get(id=book_id)
                recommended_books.append(book)
            except Book.DoesNotExist:
                continue
                
        return recommended_books
    
    def _get_popular_books(self, n=5):
        """获取热门书籍"""
        # 使用评分、点赞和借阅次数的加权和来排序
        return Book.objects.annotate(
            borrow_count=Count('bookinstance'),
            avg_rating=Avg('reviews__rating')
        ).exclude(
            Q(rating_count=0) & Q(likes=0) & Q(borrow_count=0)
        ).order_by(
            '-avg_rating', '-likes', '-borrow_count'
        )[:n]
        
    def get_hybrid_recommendations(self, user, n=10):
        """混合推荐算法，结合协同过滤和基于内容的推荐"""
        if not user.is_authenticated:
            return self._get_popular_books(n)
            
        # 获取两种推荐结果
        cf_books = self.recommend_collaborative_filtering(user, n=n//2)
        cb_books = self.recommend_content_based(user, n=n//2)
        
        # 合并并去重
        recommended_books = list(cf_books)
        recommended_book_ids = set(book.id for book in recommended_books)
        
        for book in cb_books:
            if book.id not in recommended_book_ids and len(recommended_books) < n:
                recommended_books.append(book)
                recommended_book_ids.add(book.id)
                
        # 如果推荐数量不足，补充热门书籍
        if len(recommended_books) < n:
            popular_books = self._get_popular_books(n - len(recommended_books))
            for book in popular_books:
                if book.id not in recommended_book_ids:
                    recommended_books.append(book)
                    if len(recommended_books) >= n:
                        break
                        
        return recommended_books


def get_recommendations_for_user(user, count=6):
    """获取用户的书籍推荐"""
    recommender = BookRecommender()
    return recommender.get_hybrid_recommendations(user, n=count)


def get_similar_books(book, count=4):
    """获取与指定书籍相似的书籍"""
    # 获取相同类别的书籍
    similar_books = Book.objects.filter(
        genre__in=book.genre.all()
    ).exclude(
        id=book.id
    ).distinct()
    
    # 如果同作者有其他书籍，优先推荐
    same_author_books = similar_books.filter(author=book.author)
    
    # 同类别中评分最高的书籍
    top_rated_books = similar_books.order_by('-rating', '-rating_count')[:count]
    
    # 合并结果并去重
    result = list(same_author_books)
    result_ids = set(b.id for b in result)
    
    for b in top_rated_books:
        if b.id not in result_ids and len(result) < count:
            result.append(b)
            result_ids.add(b.id)
            
    return result 